home *** CD-ROM | disk | FTP | other *** search
Text File | 1995-09-26 | 23.9 KB | 482 lines | [TEXT/CWIE] |
- #if 0
- Luminance.doc
- This file documents Luminance.c.
-
- HISTORY:
- 3/11/92 dgp added prototypes to supplement the explanations of the routines.
- 3/24/94 dgp added LToE, LToEOrdered, and EToL.
- 10/17/94 dgp updated, in response to queries by Lan Zhang.
-
- INTRODUCTION:
-
- The purpose of these subroutines is accurate control of contrast of visual
- stimuli for vision experiments. These subroutines set up the Color Lookup Table
- (CLUT) of a video framestore on the Mac II (e.g. the Apple Mac II Color Video
- Card) to properly drive a monochrome monitor (e.g. the Apple High Resolution
- Monochrome Monitor) via an Institute for Sensory Research (ISR) Video
- Attenuator. The ISR Video Attenuator consists of resistive dividers which
- attenuate the three color signal from the video framestore by different
- amounts and combine them to produce one monochrome signal.
-
- The video attenuator and the theory behind the algorithms described here are
- the topic of a paper:
- D.G. Pelli and L. Zhang (1991) Accurate control of contrast on microcomputer displays.
- Vision Research, 31:1337-1360.
-
- Achieving the goal of accurate control of contrast required solving five
- problems:
- 1. The luminance of the monitor is nonlinearly related to the input voltage
- which depends linearly on the triplet loaded into the CLUT. This is solved by
- allowing the user to provide a polynomial (I recommend 4th order) describing the
- nonlinear relationship between luminance and nominal voltage. (For computational
- reasons, you must also supply a quadratic fit.) The subroutines LToV and VToL
- use this polynomial to convert back and forth.
- 2. Affordable framestores for microcomputers have 8 bit Digital to Analog
- Converters (DACs), which can only represent 256 different luminances. This is
- not enough precision to produce threshold-contrast patterns, which are important
- for vision experiments. This is solved by the ISR Video Attenuator which
- combines the outputs of the three DACs ("red", "green", and "blue") with various
- attenuations to yield one monochrome signal which can represent low contrasts
- (by varying an attenuated DAC) and high contrasts (by varying an unattenuated
- DAC).
- 3. DACs are only accurate to ± a least significant bit (LSB). The Brooktree DACs
- used by Apple and RasterOps are specified to have a ±1 LSB "integrated
- linearity" error, which means that the voltage produced by any number will
- deviate from a line connecting the 0 and 255 points by at most 1 LSB, which is
- defined as 1/255 times the voltage difference between 0 and 255. I assume a
- slightly stronger restriction will hold, that the difference between any two
- settings will be in error by at most one LSB. So it is important to allow the
- user to specify what range of luminances will be used to present a particular
- stimulus and to fix the coarsest DACs (i.e. those with the least attenuation in
- the ISR Video Attenuator) and only vary the fine DACs to produce the specified
- luminances. The range setting is done by SetLuminanceRange() which is called
- automatically by SetLuminance() and SetLuminances().
- 4. Apple's software environment for graphics, QuickDraw, isn't designed for
- vision experiments. The facilities for creating images are fine, but the control
- of the CLUTs provided by the Palette and Color Managers is unsatisfactory for
- vision research because a lot happens behind your back. In particular the
- Palette Manager likes to maintain a consistent color environment across the
- entire desktop, which includes all your screens. Well, in vision experiments we
- generally want to treat each screen as a totally separate device, unaffected by
- what we do to the other screens. This is solved by the routine GDSetEntries()
- which makes a low-level call to the video driver to change the CLUT directly
- without QuickDraw's knowledge. (This will work with any video card that works
- with the Mac II.) The routine LoadLuminances(), which is used below, is just a
- convenient glue routine for calling GDSetEntries().
- 5. Apple specifies that the video driver should silently implement gamma
- correction to attempt to correct for the display's nonlinear relation between
- luminance and input voltage. We don't want this because this hidden gamma
- correction loses precision and interferes with the operation of the ISR Video
- Attenuator, which will seem to operate NONlinearly if this gamma correction
- takes place unbeknownst to us. This is solved by the routine GDLinearGamma()
- which makes a low-level call to the video driver and loads a linear gamma table,
- i.e. no correction.
-
- SUMMARY:
-
- double EToL(LuminanceRecord *LP,int entry);
- Returns the luminance associated with the particular entry.
-
- int LToE(LuminanceRecord *LP,double L,int firstEntry,int lastEntry);
- Returns the index of the table entry in specified range with luminance closest to L.
-
- int LtoEOrdered(LuminanceRecord *LP,double L,int firstEntry,int lastEntry);
- Returns the index of the table entry in specified range with luminance closest to L.
- Runs fast by assuming an ordered table from firstEntry to lastEntry, either
- increasing or decreasing.
-
- double SetLuminance(GDHandle device,LuminanceRecord *LP
- ,int entry,double luminance
- ,double lowLuminance,double highLuminance)
- Set one entry in the ColorSpec table (and the CLUT if device is not NULL) to
- a specified luminance. It's ok for lowLuminance to be greater than highLuminance.
- SetLuminance() sets a single entry to the specified luminance. You must also
- indicate the luminance range that you are working over, to allow optimal choice
- of which dacs to fix, etc., to yield minimum error in relative luminance.
- SetLuminances() does its work by making a call to SetLuminance() for each entry.
- SetLuminance takes 1 ms to set up the range (by calling SetLuminanceRange), which
- it only has to do once. Repeated calls to SetLuminance with the same range will
- not cause re-computation of the range. Once the range is set, SetLuminance takes
- about 0.1 ms. If SetLuminance is too slow for a real-time application you can tell
- it to just compute, but not load the new ColorSpec table (just supply NULL in place
- of device), and you can then quickly load your ColorSpec table into the CLUT
- later by calling LoadLuminances().
-
- double SetLuminancesAndRange(GDHandle device,LuminanceRecord *LP
- ,int firstEntry,int lastEntry
- ,double firstLuminance,double lastLuminance
- ,double lowLuminance,double highLuminance)
- /*
- Set a series of entries in the ColorSpec table (and the CLUT if device is not NULL)
- to a linear sequence of luminances.
- Uses last two arguments to set the luminance range of interest.
- */
- double SetLuminances(GDHandle device,LuminanceRecord *LP
- ,int firstEntry,int lastEntry
- ,double firstLuminance,double lastLuminance)
- /*
- Set a series of entries in the ColorSpec table (and the CLUT if device is not NULL)
- to a linear sequence of luminances. Assume this is the entire luminance range of
- interest.
- */
- SetLuminances() and SetLuminancesAndRange() both produce a linear relationship
- between CLUT entry and luminance over the range firstLuminance to lastLuminance,
- with minimum error relative to any luminance in the range lowLuminance to
- highLuminance. (We don't care about a small error in mean luminance.)
- (SetLuminances, which doesn't have these arguments, uses firstLuminance and
- lastLuminance to set this range.) The three DACs are linear, and the video
- attenuator will combine them linearly yielding a voltage V at the display input.
- This allows you to compute your image (e.g. a sinewave grating) and loaded to
- the frame store as a full scale linear function, i.e. with values ranging
- 0..255. Linearization of the display and setting of contrast to whatever you
- want are both accomplished by one call to SetLuminances(). The contrast can be
- changed by calling SetLuminances() again, now with a larger or smaller luminance
- range. If SetLuminances is too slow for a real-time application (its
- computations take about 8-30 ms to compute a whole 256-entry CLUT) you can tell
- it to just compute, but not load the new ColorSpec table (just supply NULL in
- place of device), and you can then quickly load your ColorSpec table into
- the CLUT later by calling LoadLuminances().
-
- double GetLuminance(GDHandle device,LuminanceRecord *LP,int entry)
- /*
- If device is not NULL then examines one entry in the actual CLUT, otherwise
- examines the ColorSpec table contained in *LP, and in either case returns the
- luminance that will be produced. Supplying an illegal entry value results in a
- returned value of -INF.
- */
- GetLuminance() returns the luminance of a single entry. If device is not NULL then
- GetLuminance will make a low-level call to the video driver to determine what is in that
- CLUT entry in the actual hardware, and will return the luminance that is expected
- to result given the channel gains and luminance nonlinearity described in your
- LuminanceRecord. If device is NULL then GetLuminance
- returns the luminance corresponding to the entry in the ColorSpec table in your
- LuminanceRecord.
-
- void IncrementLuminance(GDHandle device,LuminanceRecord *LP,int entry)
- /*
- Make smallest possible increase of the luminance of one entry in the ColorSpec table.
- This is a way to figure out what is the lowest contrast that you can produce.
- */
-
- void LoadLuminances(GDHandle device, LuminanceRecord *LP,
- int firstEntry, int lastEntry)
- /*
- This just calls GDSetEntries() to load your ColorSpec table into the CLUT of
- your screen device. It is here simply to provide a cosmetic match to the
- call to SetLuminances(), for which loading the CLUT is optional.
- Note: if you prefer, instead of LP you may send just the address of
- a ColorSpec table, cast to (LuminanceRecord *), since a LuminanceRecord
- begins with a ColorSpec table.
- Does nothing if device==NULL or the device has no driver.
- */
-
- double GetV(GDHandle device,LuminanceRecord *LP,int entry);
- double VToL(LuminanceRecord *LP,double V);
- double LToV(LuminanceRecord *LP,double L);
- double LToL(LuminanceRecord *LP,double L);
- Most users will never have any reason to use these routines. They are useful
- primarily in error analysis of the Luminance.c package.
-
- EXAMPLES:
-
- double tolerance,luminance,meanLuminance,c;
- int firstEntry,lastEntry,entry;
-
- /* load CLUT with linear luminance range, immediately */
- tolerance=SetLuminances(device,&LR,0,255,(1.-c)*meanLuminance,(1+c)*meanLuminance);
-
- /* load ColorSpec table with linear luminance range, then load CLUT */
- tolerance=SetLuminances(NULL,&LR,0,255,(1.-c)*meanLuminance,(1+c)*meanLuminance);
- LoadLuminances(device,&LR,firstEntry,lastEntry);
-
- /* load ColorSpec table with sinusoidal luminance range, then load CLUT */
- for(entry=0;entry<256;entry++) {
- luminance = meanLuminance*(1.0+c*sin(2.0*3.14159265*entry/256.));
- tolerance=SetLuminance(NULL,&LR,entry,luminance,(1.-c)*meanLuminance,(1+c)*meanLuminance);
- }
- LoadLuminances(device,&LR,firstEntry,lastEntry);
-
- /* Examine an entry in the ColorSpec table or CLUT */
- luminance=GetLuminance(device,&LR,entry); /* in CLUT */
- luminance=GetLuminance(NULL,&LR,entry); /* in ColorSpec table */
-
- NOTES:
-
- The argument firstEntry must not exceed lastEntry. They may be equal.
-
- The returned value, tolerance, is an estimate of the largest possible error in
- the luminance DIFFERENCES, taking into account the precision, accuracy (assumed
- to be ±1 least significant bit), and range of the DACs. (Note the returned
- tolerance tells you about errors in the luminance DIFFERENCES on the display.
- The error in ABSOLUTE luminance of the display is expected to be at most ±1 step
- of all the DACs, i.e. one part in 255 of full scale.)
-
- There are no restrictions at all on firstLuminance and lastLuminance. It is
- reasonable to request an impossibly large luminance range, e.g. from a negative
- luminance up to twice the maximum luminance. SetLuminances will produce the
- closest possible approximation. The luminance of each entry will be clipped to
- fit in the range of possible luminances. Thus you will obtain the requested
- contrast for pixel values that aren't clipped. You can detect the clipping by
- the fact that the returned tolerance will be very large.
-
- A common mistake in C programming is to use a pointer that supposedly points to
- a structure, without ever allocating said structure. Consider the
- LuminanceRecord. The following declaration and call will be accepted by the
- compiler, but will usually have disastrous consequences:
- LuminanceRecord *LP;
- SetLuminances(...,LP,...); /* Bad! */
- The problem is that you never allocated a LuminanceRecord, only a pointer. When
- SetLuminances starts storing information in what it thinks is a LuminanceRecord it
- will actually be writing over a random part of memory. What you should do is:
- LuminanceRecord LR;
- SetLuminances(...,&LR,...); /* Good */
- Or you can do this:
- LuminanceRecord LR,*LP;
- LP=&LR;
- SetLuminances(...,LP,...); /* Good */
- This second technique is a better habit, since if you want to put some of your code into
- a subroutine, you'll be passing the pointer, not the structure itself, so you might as well
- get used to using the pointer.
-
- Before using SetLuminance or SetLuminances you must initialize your LuminanceRecord.
- You should do that by reading in,
- LuminanceRecord LR;
- ReadLuminanceRecord("LuminanceRecord2.h",&LR,0);
- or #including a file with all the parameters describing your monitor.
- LuminanceRecord LR;
- #include "LuminanceRecord2.h"
-
- Here's an example of the contents of a LuminanceRecord1.h file, as produced by the
- program CalibrateLuminance. You must use CalibrateLuminance to calibrate
- your own framestore, ISR Video Attenuator, and display.
- LR.screen=1; /* device=GetScreenDevice(LR.screen); */
- /* Caution: the screen number used here and in GetScreen Device is NOT the same as */
- /* displayed by the Monitors cdev in the Control Panel. Sorry. The most obvious difference */
- /* is that GetScreenDevice always assigns 0 to the main screen, the one with the menu bar. */
- LR.date="3:09 PM Friday, October 16, 1992";
- LR.id="5111767";
- LR.name="signal";
- LR.notes="manoj, lights off, photometer 1 inch from screen";
- LR.dpi=76.0; /* pixels per inch */
- LR.Hz=66.67; /* frames per second */
- LR.units="cd/m^2";
- /* coefficients of polynomial fit */
- LR.coefficients=9; /* # of coefficients in polynomial fit */
- /* L(V)=p[0]+p[1]*V+p[2]*V*V+ . . . ±polynomialError */
- LR.p[0]=1.28201e-14;
- LR.p[1]=8.25776e-13;
- LR.p[2]=5.00064e-11;
- LR.p[3]=2.51138e-09;
- LR.p[4]=7.98055e-08;
- LR.p[5]=-2.04184e-09;
- LR.p[6]=1.79181e-11;
- LR.p[7]=-6.3791e-14;
- LR.p[8]=8.10888e-17;
- LR.polynomialError= 0.2263; /* RMS error of fit */
- /* coefficients of quadratic fit */
- /* L(V)=q[0]+q[1]*V+q[2]*V*V±quadraticError */
- LR.q[0]=4.86285;
- LR.q[1]=-0.201266;
- LR.q[2]=0.00125998;
- LR.quadraticError= 2.5493; /* RMS error of fit */
- /* coefficients of power law fit */
- /* L(V)=power[0]+Rectify(power[1]+power[2]*V)^power[3]±powerError */
- /* where Rectify(x)=x if x≥0, and Rectify(x)=0 otherwise */
- /* Pelli & Zhang (1991) Eqs.9&10 use symbols v=V/255, alpha=power[0], beta=power[1], kappa=power[2]*255, gamma=power[3] */
- LR.power[0]=-0.0615421;
- LR.power[1]=-3.38554;
- LR.power[2]=0.0305017;
- LR.power[3]=2.48866;
- LR.powerError= 0.2616; /* RMS error of fit */
- /* coefficients of power law fit, with fixed exponent */
- /* L(V)=fixedPower[0]+Rectify(fixedPower[1]+fixedPower[2]*V)^fixedPower[3]±fixedPowerError */
- LR.fixedPower[0]=-0.764058;
- LR.fixedPower[1]=-2.96785;
- LR.fixedPower[2]=0.0310164;
- LR.fixedPower[3]= 2.28;
- LR.fixedPowerError= 1.1986; /* RMS error of fit */
- LR.r=0.0282406;
- LR.g=0.149883;
- LR.b=0.821877;
- LR.gainAccuracy=-0.0337905;
- LR.gm=3.85543; /* The monitor's contrast gain. */
- LR.VMin= 0; /* minimum value that can be loaded into DAC */
- LR.VMax=255; /* maximum value that can be loaded into DAC */
- LR.LMin= -0.06; /* luminance at VMin */
- LR.LMax= 39.70; /* luminance at VMax */
- LR.LBackground= 9.731; /* background luminance during calibration */
- LR.VBackground=193; /* background number used during calibration */
- LR.rangeSet=0; /* indicate that range parameters have yet to be set */
- LR.L.exists=0; /* indicate that luminance table has yet to be initialized */
- Note that these parameters describe fits of three functions to the gamma function.
- You may omit any or all of these fits, but should document it by setting the
- corresponding error terms to infinity (=1.0/0.0). If you omit all of them then
- you must supply a table describing the gamma function, e.g.
- LR.L._VMin=DoubleToFix(0.); /* if possible, restrict to just the monotonic range */
- LR.L._VMax=DoubleToFix(255.); /* if possible, restrict to just the monotonic range */
- LR.L._dV=(LR.L._VMax-LR.L._VMin)/(LUMINANCES_IN_TABLE-1);
- LR.L.latestIndex=-1; /* invalid latestIndex, so hunt will start from scratch */
- LR.L.exists=luminanceSet; /* mark table as valid */
- LR.L._Lu[0]=DoubleToFix(0.57);
- LR.L._Lu[1]=DoubleToFix(0.57);
- ...
- LR.L._Lu[LUMINANCES_IN_TABLE-1]=DoubleToFix(101.13);
- "LUMINANCES_IN_TABLE" is currently defined to be 128 in Luminance.h. Normally this
- table is synthesized at run time, from one of the formulaic descriptions,
- but you may prefer to supply it directly, possibly using the raw measurements,
- and not bother with any fitting. The formulas are not used if the table is supplied.
-
- To fill in the numbers above, you have to do two calibrations. (This is what the
- CalibrateLuminance program does.)
-
- 0. Before starting, call GDLinearGamma(). (Note that GDOpenWindow
- automatically calls GDLinearGamma for you.) All calibrations should be done
- with the ISR Video Attenuator in place.
-
- 1. Measure the luminance versus "voltage" nonlinearity of your monitor.
- I use a separate program "CalibrateLuminance" to measure the screen luminance at values
- of V from 0 to 255. For this calibration you load equal values into Red, Green, and Blue
- lookup tables. I use quadratic, polynomial, and power law fits. See Pelli & Zhang (1991).
-
- You must set LR.LMin and LR.LMax to the luminances at LR.VMin and LR.VMax.
-
- The following program fragment allows you to measure the screen luminance as a function
- of nominal voltage.
-
- #include "VideoToolbox.h"
- #include "Luminance.h"
-
- void main(void)
- {
- LuminanceRecord LR;
- int V,i,done;
- double a;
- GDHandle oldDevice,device;
- CWindowPtr cWindow,oldCWindow;
- char string[32];
-
- #include "LuminanceRecord1.h"
- GetGWorld(&oldCWindow,&oldDevice);
- LR.screen=1;
- device=GetScreenDevice(LR.screen); /* screen 1 */
- cWindow=GDOpenWindow(device); /* Open a full-screen window with explicit colors */
- SetGWorld(cWindow,device);
- PmBackColor(1); /* pick a color table entry */
- EraseRect(&cWindow->portRect); /* fill whole window with that color */
- SetGWorld(oldCWindow,oldDevice);
- LR.L._VMin=DoubleToFix(0.); /* if possible, restrict to just the strictly monotonic range */
- LR.L._VMax=DoubleToFix(255.);
- done=0;
- for(i=0;i<LUMINANCES_IN_TABLE;i++){
- LR.L._dV=ceil(FixToDouble(LR.L._VMax-LR.L._VMin)/(LUMINANCES_IN_TABLE-1));
- V=FixToLong(i*LR.L._dV);
- if(V>=FixToLong(LR.L._VMax)){
- V=FixToLong(LR.L._VMax);
- done=1;
- }
- LR.table[1].rgb.red =V<<8; /* Set color table entry 1. */
- LR.table[1].rgb.green =V<<8;
- LR.table[1].rgb.blue =V<<8;
- LoadLuminances(device,&LR,1,1); /* Copy color table entry 1 to CLUT */
- printf("%3d Please measure screen luminance now, in %s, and type in:",V,LR.units);
- gets(string);
- sscanf(string,"%lf",&a);
- printf("%6.2g %s\n",a,LR.units);
- LR.L._Lu[i]=DoubleToFix(a);
- if(done)break;
- if(LR.L._Lu[i]<=LR.L._Lu[0]){ /* restrict to just the strictly monotonic range */
- LR.L._VMin=LongToFix(V);
- LR.L._Lu[0]=LR.L._Lu[i];
- i=0;
- }
- }
- LR.L.latestIndex=-1; /* invalid latestIndex, so hunt will start from scratch */
- LR.L.exists=luminanceSet; /* mark table as valid */
- }
-
- 2. Measure the gains of the three inputs of your ISR Video Attenuator. You could
- do this by using an oscilloscope to measuring the voltage at the input to the monitor.
- Instead, I measure the resulting luminance and use LToV() to infer the voltage.
- Vary one DAC while holding the other two fixed at 255 and measure the luminances.
- Use the function LToV() to convert back to a "voltage" and compute the gain of the
- varying DAC. Note that the three DACs on your framestore will generally have gains that
- match to only ±5%. This calibration is measuring the overall gain of each of the three
- pathways, including your DACs and the ISR Video Attenuator.
-
- LIMITATIONS:
-
- It is imperative that you call GDLinearGamma() at some time before using
- SetLuminances. SetLuminances ASSUMES that no gamma correction takes place. You
- only need to call GDLinearGamma once. It will stay that way until the next time
- you restart your computer. Incidentally, GDOpenWindow() calls GDLinearGamma for
- you.
-
- The luminance calibration data that you supply to SetLuminances must also have
- been collected with no gamma correction, i.e. AFTER calling GDLinearGamma.
-
- The reason that gamma correction is not allowed is that it would result in a
- nonlinear transformation of the three channels BEFORE they are combined in the
- Video Attenuator.
-
- The measurement of the gains of the three pathways must be made using YOUR
- framestore. The gains of your three DACs will in general be different from each
- other, and different from framestore to framestore. Brooktree guarantees the
- matching of the gains of the three DACs on their chip to only ±5%.
-
- The luminance record may include either a table of luminance calibrations. If it
- is not supplied then it will be synthesized from the parameters of the
- polynomial or power law fit. If the power, polynomial, or quadratic fit
- parameters are not supplied then the appropriate LR.powerError,
- LR.polynomialError, or LR.quadraticError field should be set to infinity
- (=0.0/1.0).
-
- The whole package is at present restricted to grayscale.
- There is no provision for linearizing a color monitor. In particular it is
- assumed that the the DACs are linearly combined BEFORE the display nonlinearity.
- Linearizing luminance on a color display would need to allow for three different
- nonlinear transformations.
-
- EXPLANATION OF THE CODE:
-
- Physically your framestore transforms each CLUT entry number to a voltage that
- will be linearly related to the number. The three output voltages will be
- combined by the ISR Video Attenuator to produce a single voltage which drives
- the video monitor. Finally, the display nonlinearly transforms V to produce a
- luminance L. My convention for "measuring" the "voltage" V at the input of the
- monitor is that V=0 when all three DACs are set to zero and V=255 when all three
- DACs are set to 255. (This will be linearly related to volts measured, with a
- voltmeter, at the output of the Video Attenuator.) The virtue of the attenuator
- is that it allows us to produce nonintegral values of V.
-
- There are two givens:
- 1. DACs are inaccurate. A good DAC may be specified to be merely monotonic. For
- purposes of computing the returned tolerance value, I follow the Brooktree DAC
- specification of ±1 LSB error in integrated linearity and assume that the error
- in any luminance difference is at most ±1 LSB.
- 2. We want to minimize the error in representing the waveform, but are not
- particularly worried about the exact value of the mean luminance. Thus, if we
- have DACs with different gains we may fix the coarse DACs to set the mean
- luminance and vary the fine DACs to produce the linear range of luminances
- requested.
-
- Here's the strategy. We want to cover the range L0 to Ln with the smallest
- possible error in L-L0.
-
- Steps 1 to 5 happen in SetLuminanceRange:
- 1. Sort the DACs by gain g, where gain is defined as the change in V when
- a single DAC is increased from 0 to 255, so g0+g1+g2=1. Let the gains be g0>g1>g2.
- 2. Transform the luminance range to a nominal voltage range lowV and highV.
- 3. Decide which DACs should be variable and which should be fixed,
- so as the minimize the tolerance in the luminance increments.
- 4. Temporarily set the variable DACs to their midpoints. Now set the fixed DACs to
- most accurately represent the midpoint of the nominal voltage range, (lowV+highV)/2.
- 5. If necessary, compute a small luminance offset LShift to bring the requested range
- lowV to highV into the range attainable by the variable DACs. (This is necessary
- because the centering in step 4 may not be precise enough.)
-
- Step 6 happens in _SetLuminance:
- 6. Set the variable DACs in the ColorSpec entry so as to most accurately represent
- L+LShift.
-
- SetLuminances(), SetLuminancesAndRange(), and SetLuminance() all call _SetLuminance().
- #endif